/*
 * @(#)Base64Encoder.java	1.0 Mar 7, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.io.encoding;

/**
 * Derived from RFC2045, sec 6.8 and java.util.prefs.Base64
 * ftp://ftp.rfc-editor.org/in-notes/rfc2045.txt
 */
public class Base64Encoder
{
	private static final char[] base64Alphabet =
	{
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
	};
	private static final char padChar = '=';
	private static final byte[] base64Reversed =
	{
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
        55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
        24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
        35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
	};

	public static String encode(byte[] toBase64Encode)
	{
		StringBuilder sb = new StringBuilder();
		int toEncodeLength = toBase64Encode.length;
		int numGroups = toEncodeLength / 3;
		int numPartial = toEncodeLength - (numGroups * 3);

		int position = 0;
		for(int group=0; group<numGroups; group++)
		{
			// unsign them
			int byte0 = toBase64Encode[position++] & 0xff;
			int byte1 = toBase64Encode[position++] & 0xff;
			int byte2 = toBase64Encode[position++] & 0xff;

			// encode the 3 bytes into 4 characters
			sb.append(base64Alphabet[byte0	>> 2]);
			sb.append(base64Alphabet[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
			sb.append(base64Alphabet[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
			sb.append(base64Alphabet[byte2 & 0x3f]);
		}

		if(numPartial > 0)
		{
			// unsign them
			int byte0 = toBase64Encode[position++] & 0xff;
			// encode the 3 bytes into 4 characters
			sb.append(base64Alphabet[byte0	>> 2]);
			if(numPartial == 1)
			{
				sb.append(base64Alphabet[(byte0 << 4) & 0x3f]);
				sb.append(padChar);
				sb.append(padChar);
			}else{// not 0, or 1, must be 2
				int byte1 = toBase64Encode[position++] & 0xff;
				sb.append(base64Alphabet[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
				sb.append(base64Alphabet[(byte1 << 2) & 0x3f]);
				sb.append(padChar);
			}
		}

		return sb.toString();
	}

	public static byte[] decode(String base64Encoded)
	{
		int encodedLength = base64Encoded.length();
		int numGroups = encodedLength/4;
		int numPads = 0;

		if(base64Encoded.charAt(encodedLength - 1) == padChar)
			numPads++;

		if(base64Encoded.charAt(encodedLength - 2) == padChar)
			numPads++;

		int fullGroups = ((numPads == 0)?numGroups:numGroups-1);
		byte[] buf = new byte[3 * numGroups - numPads];

		int encodedPos = 0;
		int bufPos = 0;
		for(int i=0; i<fullGroups; i++)
		{
			int char0 = base64Reversed[base64Encoded.charAt(encodedPos++)];
			int char1 = base64Reversed[base64Encoded.charAt(encodedPos++)];
			int char2 = base64Reversed[base64Encoded.charAt(encodedPos++)];
			int char3 = base64Reversed[base64Encoded.charAt(encodedPos++)];

			buf[bufPos++] = (byte)((char0 << 2) | (char1 >> 4));
			buf[bufPos++] = (byte)((char1 << 4) | (char2 >> 2));
			buf[bufPos++] = (byte)((char2 << 6) | char3);
		}

		if(numPads > 0)
		{
			int char0 = base64Reversed[base64Encoded.charAt(encodedPos++)];
			int char1 = base64Reversed[base64Encoded.charAt(encodedPos++)];

			buf[bufPos++] = (byte)((char0 << 2) | (char1 >> 4));

			if(numPads == 1)
			{
				int char2 = base64Reversed[base64Encoded.charAt(encodedPos++)];
				buf[bufPos++] = (byte)((char1 << 4) | (char2 >> 2));
			}
		}

		return buf;
	}
}
